home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Games of Daze
/
Infomagic - Games of Daze (Summer 1995) (Disc 1 of 2).iso
/
x2ftp
/
msdos
/
source
/
scrasm
/
scroll.inc
< prev
next >
Wrap
Text File
|
1993-03-08
|
19KB
|
441 lines
;; Global variables used here ...
EVEN
ScrollPosX dw 0 ; Scroll origin, upper-left X
ScrollPosY dw 0 ; Scroll origin, upper-left Y
ScrollDX dw 0 ; Amount to change scroll origin, X
ScrollDY dw 0 ; Amount to change scroll origin, Y
;; SCROLL:
;; This routine takes care of all of the scrolling, however it calls
;; outside drawing routines to update the screen. Scrollx and
;; Scrolly determine the amount to scroll by.
;; Note that this does only RELATIVE scrolling, not absolute scrolling.
;; Scroll saves time by updating only up to the one row or column of
;; tiles which have come into view due to a change in scroll offset.
;; In other words, it's not good for "jumping" to a particular point,
;; although this effect can be accomplished in other ways -- the draw_full
;; routine is available to draw a full screen again.
;; Sometimes this means that you will have to calculate values ahead of
;; time, for instance if you wish the scrolling to keep a certain sprite
;; in the center of the screen. In this case, just set ScrollDX and
;; ScrollDY to the delta-x and delta-y of the sprite.
;; * Newly added:
;; Since there are three pages, it is necessary to keep each one of them
;; up to date with each scroll. Recently, I was doing some fast (8+
;; pixels per frame) scrolling and noticed that there was a significant
;; pause when the screen snapped to a new origin. (The origin is always
;; at a square's corner, even though it may not look like it because it
;; disguises things by smooth-panning the hardware.) Every time it
;; scrolled, it was drawing the new information and copying it to the
;; two other planes. I've now distributed the load over successive
;; pages, in other words it doesn't copy the new info all at once, but
;; over several frames. This really smoothed out the scrolling so that
;; while there are still some jumps, they only occur very infrequently
;; and then only at 15 or 16 pixel/frame scroll rates...) That's the
;; "catchup" code at the bottom, and that's why it's more complex than
;; it maybe could be...
EVEN
Scroll PROC near
; Using the ScrollDX variable as delta-x, move the scroll-origin
; in the x direction. Then, if the visible screen is now
; viewing invalid data, snap the origin to a new point and
; draw any new columns that are necessary.
do_x_scroll: mov ax,cs:ScrollPosX
add ax,cs:ScrollDX ; ScrollDX is a delta-x
jl wrap_l ; wrap left if negative
cmp ax,VIRTUAL_WIDTH - SCREEN_WIDTH ; too far right?
jge wrap_r ; wrap right if too big
mov cs:ScrollPosX,ax ; Stores new scroll-x
; (just like above, for y:)
; Using the ScrollDY variable as delta-y, move the scroll-origin
; in the y direction. Then, if the visible screen is now
; viewing invalid data, snap the origin to a new point and
; draw any new rows that are necessary.
do_y_scroll: mov ax,cs:ScrollPosY
add ax,cs:ScrollDY ; ScrollDY is a delta-y
jl wrap_t ; wrap top if negative
cmp ax,(VIRTUAL_HEIGHT - SCREEN_HEIGHT) * VIRTUAL_WIDTH
jge wrap_b ; wrap bottom if too big
mov cs:ScrollPosY,ax ; Store the new scroll-y
jmp calculate
; To wrap to the right:
; Add a square's width to the origin's upper left corner, and
; subtract the same amount from the scroll origin's upper left
; corner. This makes no difference on the screen but allows
; us to forget about the leftmost column on the screen (it's
; offscreen now...) so we can take over the right column.
; See any documentation I included for an explanation of the
EVEN ; scrolling...
wrap_r: add cs:upper_left,SQUARE_WIDTH / 4
sub ax,SQUARE_WIDTH
mov cs:ScrollPosX,ax
mov dx,MapInfo.Wid
mov bp,MapInfo.OffX1
inc bp
cmp bp,dx
jb wrap_r1_ok
sub bp,dx
wrap_r1_ok: mov MapInfo.OffX1,bp
mov bp,MapInfo.OffX2
inc bp
cmp bp,dx
jb wrap_r2_ok
sub bp,dx
wrap_r2_ok: mov MapInfo.OffX2,bp
mov bp,MapInfo.WrapX
dec bp
jnz wrap_r3_ok
add bp,dx
wrap_r3_ok: mov MapInfo.WrapX,bp
call update_right
jmp do_y_scroll ; Jump back to do Y
EVEN ; Same for left side
wrap_l: sub cs:upper_left,SQUARE_WIDTH / 4
add ax,SQUARE_WIDTH
mov cs:ScrollPosX,ax
mov dx,MapInfo.Wid
mov bp,MapInfo.OffX1
dec bp
cmp bp,dx
jb wrap_l1_ok
add bp,dx
wrap_l1_ok: mov MapInfo.OffX1,bp
mov bp,MapInfo.OffX2
dec bp
cmp bp,dx
jb wrap_l2_ok
add bp,dx
wrap_l2_ok: mov MapInfo.OffX2,bp
mov bp,MapInfo.WrapX
inc bp
cmp bp,dx
jbe wrap_l3_ok
sub bp,dx
wrap_l3_ok: mov MapInfo.WrapX,bp
call update_left
jmp do_y_scroll ; Jump back to do Y
EVEN ; Same for bottom
wrap_b: add cs:upper_left,(SQUARE_HEIGHT * VIRTUAL_WIDTH) / 4
sub ax,SQUARE_HEIGHT * VIRTUAL_WIDTH
mov cs:ScrollPosY,ax
mov bp,MapInfo.OffY1
mov dx,MapInfo.Extent
add bp,MapInfo.Wid
cmp bp,dx
jb wrap_b1_ok
sub bp,dx
wrap_b1_ok: mov MapInfo.OffY1,bp
mov bp,MapInfo.OffY2
add bp,MapInfo.Wid
cmp bp,dx
jb wrap_b2_ok
sub bp,dx
wrap_b2_ok: mov MapInfo.OffY2,bp
mov dx,MapInfo.Ht
mov bp,MapInfo.WrapY
dec bp
jg wrap_b3_ok
add bp,dx
wrap_b3_ok: mov MapInfo.WrapY,bp
call update_bottom
mov ax,cs:ScrollPosY
jmp calculate ; Jump down to calc new offsets
EVEN ; Same for top
wrap_t: sub cs:upper_left,(SQUARE_HEIGHT * VIRTUAL_WIDTH) / 4
add ax,SQUARE_HEIGHT * VIRTUAL_WIDTH
mov cs:ScrollPosY,ax
mov bp,MapInfo.OffY1
mov dx,MapInfo.Extent
sub bp,MapInfo.Wid
cmp bp,dx
jb wrap_t1_ok
add bp,dx
wrap_t1_ok: mov MapInfo.OffY1,bp
mov bp,MapInfo.OffY2
sub bp,MapInfo.Wid
cmp bp,dx
jb wrap_t2_ok
add bp,dx
wrap_t2_ok: mov MapInfo.OffY2,bp
mov bp,MapInfo.WrapY
mov dx,MapInfo.Ht
inc bp
cmp bp,dx
jbe wrap_t3_ok
sub bp,dx
wrap_t3_ok: mov MapInfo.WrapY,bp
call update_top
mov ax,cs:ScrollPosY
jmp calculate ; Jump down to calc new offsets
EVEN
align_mask_table DB 11h,22h,44h,88h
calculate:
; Calculate the scroll offset
; AX already = ScrollPosY
add ax,cs:ScrollPosX ;Now AX = scroll offset
; Calculate the plane alignment
mov bl,al
and bx,0003h
mov cs:DrawPage.Alignment,bl
; mov bl,cs:align_mask_table[bx]
; mov cs:DrawPage.AlignmentMask,bl
; Now we don't need Scroll Offset on a pixel level any more,
; so shift it to a byte level (/4) and store it away.
shr ax,2
mov cs:DrawPage.ScrollOffset,ax
; Calculate the actual upper left corner address
mov si,cs:DrawPage.Address
add si,cs:upper_left
mov cs:DrawPage.UpperLeftAddress,si
; And the map offset:
mov bx,MapInfo.WrapX
mov cs:DrawPage.MapPosX,bx
mov di,MapInfo.WrapY
mov cs:DrawPage.MapPosY,di
mov cs:DrawPage.Valid,1
cmp cs:BlankPage.Valid,0
je no_catch_up
; Lastly, update dirty area (if any) on blank page.
; BP still contains the draw page's mapoffset.
sub bx,cs:BlankPage.MapPosX
sub di,cs:BlankPage.MapPosY
jnz yes_catch_up
cmp bx,0
jnz yes_catch_up
; No catchup necessary -- return.
no_catch_up: ret
;; Okay, this stuff is a mess. I've registerized everything except
;; for the video data itself. I'll try to comment it best I can.
EVEN
yes_catch_up:
; First, switch into full-copy mode. This means latching the
; bit mask as coming entirely from the local 32-bit registers
; and then setting the map mask to write to all 4 planes. This
; is Mode X's greatest advantage, when you can do it! It
; provides a 2x speedup or so...
mov dx,SC_INDEX ; Select Sequencer input
mov ax,0F02h
out dx,ax ; set map mask = all bits
mov dx,GC_INDEX
mov ax,ALL_COPY_BITS
out dx,ax
JKEYNP kB,isntbp
isbp: nop
isntbp:
; Next, calculate the amount to catch up the top/bottom rows
; If we just wrapped over the edge, it is possible that the
; distance traveled will be as high as MapInfo.Ht - 1. So,
; in the fashion of signed numbers, if the number is greater
; than MapInfo.Ht / 2, we take it to mean negative. To convert
; it to signed, we have to shift it into the proper range. But
; if it's less than MapInfo.Ht / 2, then it's okay as it is.
mov ax,di
cmp ax,0
je y_mod
mov cx,MapInfo.Ht
cwd ; DX = -1 or 0 based on AX's sign.
and dx,cx ; DX = Ht or 0
add ax,dx ; AX = 0 ... Ht (unsigned)
mov di,ax
shl di,1
cmp di,cx
jb y_signed
sub ax,cx
y_signed: neg ax
; Find DI MOD MapInfo.Wid, and then convert to it into virtual
; coordinates from map offset coordinates.
; This routine also calculates BP, which will be used as a loop
; counter to determine how many rows to draw on the left/right
; column copy.
y_mod: mov bp,ax
cwd
add bp,dx
xor bp,dx
shl bp,3 ; BP = (SQUARE_HEIGHT / 2) * dX
mov di,cs:MultVirtWidth[bp] ; Use multiplication table
add di,dx ; to calculate new DI, then
xor di,dx ; restore the sign.
sub bp,VIRTUAL_HEIGHT / 2
; Out: DI = # of pixels traveled,
; BP = (VIRTUAL_HEIGHT - # of rows) / 2
; Change BX (delta-x) to signed from unsigned, store in AX
mov ax,bx
mov cx,MapInfo.Wid
cwd
and dx,cx ; DX = Wid or 0
add ax,dx ; AX = 0 ... Wid
mov bx,ax
shl bx,1
cmp bx,cx
jb x_signed
sub ax,cx
x_signed:
; The following is an optimization which would slow down on
; normal memory, but I believe it will be okay on VGA memory,
; which is so incredibly slow. Basically, I've replaced all
; "rep movsb"'s with a loop that first calculates "bx = di - si",
; and then loops performing "mov ds:[si],es:[si+bx]". Why?
; Because of several reasons, none of which I'm sure actually
; help out, but they do make for smaller code. 1) It means that
; I only have to maintain SI, and "DI" is maintained automatically
; (because DI - SI should remain constant). 2) Don't have to
; calculate DS. Not much gain here. 3) Because I'd already
; unrolled the loops, and the "rep movsb"'s had become instead
; "mov al, ds:[si] / mov es:[di], al / mov al, ds:[si + 1] /
; mov es:[di + 1],al ... etc ... add si, 4 / add di, 4". In
; other words, I wasn't using MOVSB anyway. The only advantage
; I can see in MOVSB is that it doesn't have to store the answer
; in AL so it could be slightly faster. By unrolling the loops,
; I'd already made up for that, I think. 4) Normally, using
; [SI + BX + 1] would incur a penalty of an additional clock
; cycle (because it has to add two indexs + an offset). But
; the VGA memory and the '86 CPU can multi-task, and the VGA
; is very slow. So by the time the VGA is ready to write the
; next byte, the one extra clock cycle has already passed.
;
; Am I right? Does this make things faster? I have no idea.
; I haven't bothered to check both ways. Please let me know
; if I've missed something important...
;
; Here's the calculation of BX. SI is already set.
; si already = DrawPage.UpperLeftAddress
mov bx,cs:BlankPage.Address
sub bx,cs:DrawPage.Address
; Now, converts SI into "1/4" units. I do all the calculations
; in "1/4" scale and then scale back up, mostly because it saved
; me some instructions elsewhere.
shr si,2
; Stores this value of SI. This will be restored after doing
; the top/bottom copying.
mov dx,si
; Check if it's necessary to catch up the top or bottom.
catchup_tb: cmp di,0
je catchup_tb_end
jl catchup_t
catchup_b: ; COPY BOTTOM
; Move SI to point at the bottom of the screen - # of rows
; to update.
add si,((VIRTUAL_WIDTH * VIRTUAL_HEIGHT) / 4) / 4
sub si,di
jmp copy_tb
catchup_t: ; COPY_TOP
; Leave SI, but add to the "pushed" value of SI the number of
; rows that will be drawn. This prevents overlap between top
; and right/left when moving diagonally. Also, DI = |DI|
neg di
add dx,di
; Now do the actual copying. Shifts SI back into scale "1",
; then performs an unrolled loop to copy the entire virtual
; width * # of pixel rows. Since DI is already in "1/4" scale,
; it is only decremented once for each four pixels drawn.
copy_tb: shl si,2
copy_tb_loop: mov cl,es:[si]
mov es:[si+bx],cl
mov cl,es:[si+1]
mov es:[si+bx+1],cl
mov cl,es:[si+2]
mov es:[si+bx+2],cl
mov cl,es:[si+3]
mov es:[si+bx+3],cl
add si,4
dec di
jnz copy_tb_loop
catchup_tb_end:
; Next, check to see if it's necessary to draw the right or
; the left side.
catchup_rl: cmp ax,0
je catchup_rl_end
jg catchup_l
catchup_r: ; COPY RIGHT
; Adds to the "pushed" SI the width of the screen, minus
; the number of rows to be drawn.
neg ax
add dx,(VIRTUAL_WIDTH / 4) / 4
sub dx,ax
catchup_l: ; COPY LEFT (or nothing)
; Does the actual copying. First pops SI from its stored value
; and shifts it back into scale "1"
copy_rl: mov si,dx
shl si,2
; This is a loop over BP -- which has already been set as
; VIRTUAL_HEIGHT - (# of bytes drawn in vertical update)
; Again, this loop is unrolled such that it does two rows @
; 4 bytes each with every iteration.
; This LEA instruction is just a quick MOV DI, SI + 2 *y
; DI is used to push the next value of SI for each iteration
; of the loop.
copy_rl_loop: lea di,[si + 2*(VIRTUAL_WIDTH/4)]
mov cx,ax
copy_rl_col: mov dl,es:[si]
mov es:[si+bx],dl
mov dl,es:[si+1]
mov es:[si+bx+1],dl
mov dl,es:[si+2]
mov es:[si+bx+2],dl
mov dl,es:[si+3]
mov es:[si+bx+3],dl
mov dl,es:[si+VIRTUAL_WIDTH/4]
mov es:[si+bx+VIRTUAL_WIDTH/4],dl
mov dl,es:[si+VIRTUAL_WIDTH/4+1]
mov es:[si+bx+VIRTUAL_WIDTH/4+1],dl
mov dl,es:[si+VIRTUAL_WIDTH/4+2]
mov es:[si+bx+VIRTUAL_WIDTH/4+2],dl
mov dl,es:[si+VIRTUAL_WIDTH/4+3]
mov es:[si+bx+VIRTUAL_WIDTH/4+3],dl
add si,4
dec cx
jnz copy_rl_col
mov si,di ; SI = pop (SI + VIRTUAL_WIDTH/4)
inc bp ; (BP is negative, so INC it)
jnz copy_rl_loop
catchup_rl_end:
; Switch back to all-draw mode.
mov dx,GC_INDEX
mov ax,ALL_DRAW_BITS
out dx,ax
ret
Scroll ENDP